package com.ideaaedi.zoo.example.springboot.litefeature.cs.controller;

import cn.hutool.extra.servlet.JakartaServletUtil;
import com.ideaaedi.commonds.monitor.WatchTimeUtil;
import com.ideaaedi.commonds.time.DateTimeConverter;
import com.ideaaedi.commonspring.lite.antidupli.AntiDuplication;
import com.ideaaedi.commonspring.lite.monitor.WatchTime;
import com.ideaaedi.commonspring.lite.ratelimiter.RateLimiter;
import com.ideaaedi.zoo.commonbase.injector.ExtAutowiredInjector;
import com.ideaaedi.zoo.diy.feature.litefeature.api.face.LockFaceUtil;
import com.ideaaedi.zoo.diy.feature.litefeature.api.lock.Lock;
import com.ideaaedi.zoo.example.springboot.litefeature.cs.entity.DemoUserListReqVO;
import com.ideaaedi.zoo.example.springboot.litefeature.cs.entity.DemoUserListRespVO;
import com.ideaaedi.zoo.example.springboot.litefeature.cs.entity.PageInfo;
import com.ideaaedi.zoo.example.springboot.litefeature.cs.entity.Result;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.redisson.RedissonObject;
import org.redisson.api.RedissonClient;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.concurrent.TimeUnit;

@SuppressWarnings("removal")
@Validated
@RestController
@RequestMapping("/lite-feature")
public class DemoUserController {
    
    /*
     * 进出日志观察： 访问这些方法时，观察控制台进出口日志输出即可
     */

    
    /**
     * <pre>
     * 测试方式：连续发出好几个请求，观察响应、控制台报错输出
     * </pre>
     */
    @PostMapping("/anti-dupli/demo")
    @AntiDuplication(spel = "zhangsan")
    public Result<PageInfo<DemoUserListRespVO>> antiDupliDemo(@Validated @RequestBody DemoUserListReqVO req) {
        return Result.success();
    }
    
    /**
     * 分布式锁 使用方式一：直接使用门面类{@link LockFaceUtil}
     * <pre>
     * 测试方式：连续发出好几个请求，观察响应、控制台报错输出
     * </pre>
     */
    @GetMapping("/lock/demo1/{userId}")
    public Result<Object> lockDemo1(@PathVariable("userId") Long userId) {
        ///try {
//            LockFaceUtil.get(userId + "").voidExec(
            LockFaceUtil.get(userId + "", 3).voidExec(
                    () -> {
                        System.err.println(userId + " 开始做逻辑.");
                        try {
                            TimeUnit.SECONDS.sleep(5);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
    
                        System.err.println(userId + " 结束做逻辑.");
                    }
            );
        ///} catch (NotAcquiredLockException e) {
        ///    return Result.failure("获取锁失败" + e.toString());
        ///}
        return Result.success();
    }
    
    /**
     * 分布式锁 使用方式二：使用注解{@link Lock}
     * <pre>
     * 测试方式：连续发出好几个请求，观察响应、控制台报错输出
     * </pre>
     */
    @GetMapping("/lock/demo2/{userId}")
//    @Lock(lockKey = "#{#userId}")
    @Lock(lockKey = "#{#userId}", waitTime = 3)
    public Result<String> lockDemo2(@PathVariable("userId") Long userId) {
        System.err.println(userId + " 开始做逻辑.");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.err.println(userId + " 结束做逻辑.");
        return Result.success();
    }
    
    /**
     * ExtAutowiredInjector 使用方式一：{@link ExtAutowiredInjector#inject(Object)}
     */
    @GetMapping("/ExtAutowiredInjector/demo1")
    public Result<String> extDemo1() {
        Student1 student1 = new Student1();
        ExtAutowiredInjector.inject(student1);
        Assert.notNull(student1.redissonClient, "redissonClient cannot be null");
        return Result.success(student1.redissonClient.getId());
    }
    
    /**
     * ExtAutowiredInjector 使用方式二：直接继承{@link ExtAutowiredInjector}
     */
    @GetMapping("/ExtAutowiredInjector/demo2")
    public Result<String> extDemo2() {
        Student2 student2 = new Student2();
        Assert.notNull(student2.redissonClient, "redissonClient cannot be null");
        return Result.success(student2.redissonClient.getId());
    }

    /**
     * 耗时统计
     */
    @WatchTime("任务A")
    @GetMapping("/timeWatcher/demo1")
    public Result<String> timeWatcherDemo1() {
        return WatchTimeUtil.watch("任务B", () -> {
            Student2 student2 = new Student2();
            Assert.notNull(student2.redissonClient, "redissonClient cannot be null");
            return Result.success(student2.redissonClient.getId());
        });
    }

    /**
     * 耗时统计
     */
    @GetMapping("/timeWatcher/demo2")
    public Result<String> timeWatcherDemo2() {
        Student2 student2 = new Student2();
        Assert.notNull(student2.redissonClient, "redissonClient cannot be null");
        return Result.success(student2.redissonClient.getId());
    }


    /**
     * <pre>
     * redis-key中为什么设置有【{】和【}】，这是因为：
     *    1. 如果你的redis-key中没有【{】的话，那么 redisson 在存储限流实时数据时，会自动追加一个【{】到redis-key的最前面。详见源码{@link RedissonObject#prefixName(String, String)}
     *    2. 如果你的redis-key中没有【}】的话，那么 redisson 在存储限流实时数据时，会自动追加一个【}】到redis-key的最后面。详见源码{@link RedissonObject#suffixName(String, String)}
     * 与其让【{】、【}】加到两端，还不如主动让其出现在我们redis-key的中间
     *
     * <hr />
     * 举例说明：
     * 假设你的限流器名为
     * {@code RRateLimiter rateLimiter = redissonClient.getRateLimiter("xyz:qwer");}
     * 那么最终生成的相关reids-key有：
     *     1.【xyz:qwer】： 用于存储你对限流器的限流配置
     *     2.【xyz:qwer:permits】、【xyz:qwer:value】：用于存储限流实时数据
     * </pre>
     */
    public static final String RATE_LIMITER_REDIS_KEY_PREFIX = "rate-limiter:{-}";


    /**
     * 限流器
     * <p>
     * 注：要验证限流器，需要先启动项目，然后再运行单元测试com.ideaaedi.zoo.example.springboot.litefeature.cs.controller.RateLimiterTest#testRateLimiter()
     * </p>
     */
    @GetMapping("/RateLimiter/demo")
    @RateLimiter(
            uid = "#{T(com.ideaaedi.zoo.example.springboot.litefeature.cs.controller.DemoUserController).buildRateLimitUid()}",
            tokenLimit = 10,
            interval = 10
    )
    public String rateLimiterDemo() {
        return "成功. " + DateTimeConverter.REGULAR_DATE_TIME.now();
    }
    
    
    
    private static class Student1 {
        
        @Resource
        RedissonClient redissonClient;

    }
    
    
    
    private static class Student2 extends ExtAutowiredInjector {
        
        @Resource
        RedissonClient redissonClient;

    }

    public static String buildRateLimitUid() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();

        String servletPath = httpServletRequest.getServletPath();
        String clientIp = JakartaServletUtil.getClientIP(httpServletRequest);
        return RATE_LIMITER_REDIS_KEY_PREFIX + ":" + servletPath + ":" + clientIp;
    }
}
